Add minimum span duration filtering#17299
Conversation
Add dashboard trace-detail filtering for spans shorter than a configured duration, expose the same minDurationMs filter through telemetry APIs, and add CLI --min-duration support for spans and traces. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 17299Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 17299" |
There was a problem hiding this comment.
Pull request overview
Adds a minimum span duration filter across the Dashboard telemetry APIs, Dashboard trace detail UI, and Aspire CLI telemetry commands so very short spans can be hidden to reduce trace noise.
Changes:
- Adds
minDurationMsquery support to dashboard telemetry endpoints (spans, traces, trace detail) and applies filtering inTelemetryApiService. - Adds a “Minimum duration (ms)” filter control to the Trace Detail page and filters spans client-side in the grid.
- Adds
--min-duration/--min-duration-msto CLIotel spans/otel traces, plus test coverage and localization resources.
Show a summary per file
| File | Description |
|---|---|
| tests/Aspire.Dashboard.Tests/TelemetryApiServiceTests.cs | Adds unit tests for minimum-duration filtering behavior in telemetry APIs. |
| tests/Aspire.Dashboard.Tests/DashboardUrlsTests.cs | Verifies minDurationMs is appended to telemetry API URLs. |
| tests/Aspire.Dashboard.Components.Tests/Shared/FluentUISetupHelpers.cs | Updates Fluent UI JS interop test setup for text/number fields. |
| tests/Aspire.Dashboard.Components.Tests/Pages/TraceDetailsTests.cs | Adds component test for minimum-duration filtering in trace details grid. |
| tests/Aspire.Cli.Tests/Commands/TelemetryTracesCommandTests.cs | Tests CLI traces command includes minDurationMs in requests. |
| tests/Aspire.Cli.Tests/Commands/TelemetrySpansCommandTests.cs | Tests CLI spans command includes minDurationMs in requests. |
| src/Shared/DashboardUrls.cs | Adds optional minDurationMs query parameter to telemetry URL builders. |
| src/Aspire.Dashboard/DashboardEndpointsBuilder.cs | Wires minDurationMs from query string into telemetry API service calls. |
| src/Aspire.Dashboard/Api/TelemetryApiService.cs | Implements minimum-duration filtering for spans/traces/trace detail + follow streaming. |
| src/Aspire.Dashboard/Components/Pages/TraceDetail.razor | Adds minimum-duration input control (desktop + mobile toolbars). |
| src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.cs | Applies minimum-duration filter when computing visible span view models. |
| src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.css | Styles the new minimum-duration filter UI. |
| src/Aspire.Dashboard/Resources/TraceDetail.resx | Adds localized strings for the new UI label/placeholder/title. |
| src/Aspire.Dashboard/Resources/TraceDetail.Designer.cs | Generated accessors for new TraceDetail resource strings. |
| src/Aspire.Dashboard/Resources/xlf/TraceDetail.cs.xlf | Localization entries for new TraceDetail strings (cs). |
| src/Aspire.Dashboard/Resources/xlf/TraceDetail.de.xlf | Localization entries for new TraceDetail strings (de). |
| src/Aspire.Dashboard/Resources/xlf/TraceDetail.es.xlf | Localization entries for new TraceDetail strings (es). |
| src/Aspire.Dashboard/Resources/xlf/TraceDetail.fr.xlf | Localization entries for new TraceDetail strings (fr). |
| src/Aspire.Dashboard/Resources/xlf/TraceDetail.it.xlf | Localization entries for new TraceDetail strings (it). |
| src/Aspire.Dashboard/Resources/xlf/TraceDetail.ja.xlf | Localization entries for new TraceDetail strings (ja). |
| src/Aspire.Dashboard/Resources/xlf/TraceDetail.ko.xlf | Localization entries for new TraceDetail strings (ko). |
| src/Aspire.Dashboard/Resources/xlf/TraceDetail.pl.xlf | Localization entries for new TraceDetail strings (pl). |
| src/Aspire.Dashboard/Resources/xlf/TraceDetail.pt-BR.xlf | Localization entries for new TraceDetail strings (pt-BR). |
| src/Aspire.Dashboard/Resources/xlf/TraceDetail.ru.xlf | Localization entries for new TraceDetail strings (ru). |
| src/Aspire.Dashboard/Resources/xlf/TraceDetail.tr.xlf | Localization entries for new TraceDetail strings (tr). |
| src/Aspire.Dashboard/Resources/xlf/TraceDetail.zh-Hans.xlf | Localization entries for new TraceDetail strings (zh-Hans). |
| src/Aspire.Dashboard/Resources/xlf/TraceDetail.zh-Hant.xlf | Localization entries for new TraceDetail strings (zh-Hant). |
| src/Aspire.Cli/Commands/TelemetryCommandHelpers.cs | Introduces the shared --min-duration/--min-duration-ms option definition. |
| src/Aspire.Cli/Commands/TelemetrySpansCommand.cs | Passes minimum duration through to spans API URL. |
| src/Aspire.Cli/Commands/TelemetryTracesCommand.cs | Passes minimum duration through to traces/trace detail API URLs. |
| src/Aspire.Cli/Resources/TelemetryCommandStrings.resx | Adds description string for the new CLI option. |
| src/Aspire.Cli/Resources/TelemetryCommandStrings.Designer.cs | Generated accessor for the new CLI option description. |
| src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.cs.xlf | Localization entry for new CLI option description (cs). |
| src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.de.xlf | Localization entry for new CLI option description (de). |
| src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.es.xlf | Localization entry for new CLI option description (es). |
| src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.fr.xlf | Localization entry for new CLI option description (fr). |
| src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.it.xlf | Localization entry for new CLI option description (it). |
| src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.ja.xlf | Localization entry for new CLI option description (ja). |
| src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.ko.xlf | Localization entry for new CLI option description (ko). |
| src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.pl.xlf | Localization entry for new CLI option description (pl). |
| src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.pt-BR.xlf | Localization entry for new CLI option description (pt-BR). |
| src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.ru.xlf | Localization entry for new CLI option description (ru). |
| src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.tr.xlf | Localization entry for new CLI option description (tr). |
| src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.zh-Hans.xlf | Localization entry for new CLI option description (zh-Hans). |
| src/Aspire.Cli/Resources/xlf/TelemetryCommandStrings.zh-Hant.xlf | Localization entry for new CLI option description (zh-Hant). |
Copilot's findings
Files not reviewed (2)
- src/Aspire.Cli/Resources/TelemetryCommandStrings.Designer.cs: Language not supported
- src/Aspire.Dashboard/Resources/TraceDetail.Designer.cs: Language not supported
Comments suppressed due to low confidence (2)
src/Aspire.Cli/Commands/TelemetrySpansCommand.cs:101
- --min-duration accepts negative/NaN/Infinity values and they’ll be silently treated as “no duration filter” by the dashboard API (since minDurationMs <= 0 or non-finite becomes null). This makes the CLI option misleading. Add validation similar to --limit (e.g., require a finite value >= 0, or reject < 0/non-finite with an InvalidCommand exit and a clear error message).
var traceId = parseResult.GetValue(s_traceIdOption);
var hasError = parseResult.GetValue(s_hasErrorOption);
var dashboardUrl = parseResult.GetValue(s_dashboardUrlOption);
var apiKey = parseResult.GetValue(s_apiKeyOption);
var search = parseResult.GetValue(s_searchOption);
var minimumDuration = parseResult.GetValue(s_minimumDurationOption);
// Validate --limit value
if (limit.HasValue && limit.Value < 1)
{
return CommandResult.Failure(CliExitCodes.InvalidCommand, TelemetryCommandStrings.LimitMustBePositive);
}
src/Aspire.Cli/Commands/TelemetryTracesCommand.cs:99
- --min-duration accepts negative/NaN/Infinity values and they’ll be silently treated as “no duration filter” by the dashboard API (minDurationMs <= 0 or non-finite becomes null). This makes the CLI option misleading. Add validation similar to --limit (e.g., require a finite value >= 0, or reject < 0/non-finite with an InvalidCommand exit and a clear error message).
var format = parseResult.GetValue(s_formatOption);
var limit = parseResult.GetValue(s_limitOption);
var traceId = parseResult.GetValue(s_traceIdOption);
var hasError = parseResult.GetValue(s_hasErrorOption);
var dashboardUrl = parseResult.GetValue(s_dashboardUrlOption);
var apiKey = parseResult.GetValue(s_apiKeyOption);
var search = parseResult.GetValue(s_searchOption);
var minimumDuration = parseResult.GetValue(s_minimumDurationOption);
// Validate --limit value
if (limit.HasValue && limit.Value < 1)
{
return CommandResult.Failure(CliExitCodes.InvalidCommand, TelemetryCommandStrings.LimitMustBePositive);
}
- Files reviewed: 43/45 changed files
- Comments generated: 2
# Conflicts: # src/Aspire.Dashboard/Components/Pages/TraceDetail.razor # src/Aspire.Dashboard/Components/Pages/TraceDetail.razor.css
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Fixed values don’t cut it (though we can be smart about calculating a scale). I think it needs to be possible to enter a value. I am too worried about horizontal space as it doesn’t scale well for general filters |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Acknowledged. I addressed the performance issue from the inline thread and pushed the fix. |
PR Testing ReportPR Information
CLI Version Verification
Changes AnalyzedThe PR adds minimum span duration filtering to CLI telemetry commands and dashboard trace-detail views/APIs, including:
Test Scenarios ExecutedScenario 1: Dogfood CLI install and option discoveryObjective: Verify the PR dogfood artifact is available, matches the PR head, and exposes the new CLI options. Result: Passed Evidence:
Scenario 2: Fresh starter app restore/build with PR packagesObjective: Verify a new Aspire starter app can be created and built using the PR package channel. Steps:
Result: Passed Evidence:
Scenario 3: CLI duration filter query propagation with mock dashboard APIObjective: Verify the PR CLI sends the expected Steps:
Result: Passed Captured requests: Scenario 4: Live AppHost telemetry filteringObjective: Exercise the filter against a running starter AppHost and dashboard. Result: Inconclusive Observation: A starter AppHost launched successfully, but the live telemetry run was not reliable in this agent environment: one attempt produced empty telemetry arrays, and a later follow-up could not reconnect to the dashboard after startup. I did not count this as a PR failure because the controlled dashboard API scenario verified the CLI request behavior added by this PR. Summary
Overall ResultPassed for the validated CLI/package scenarios. I did not find a PR-specific failure in the executed coverage. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
|
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Traces should support duration filter. It would work on the duration of the entire trace. But that can happen in a future PR. |
@JamesNK file that issue plz. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Treat trace duration filters as per-span noise filters so a long root span no longer brings every short child back into view. Keep ancestors of matching spans visible for waterfall navigation and add regression coverage for the long-root/short-child case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Re-running the failed jobs in the CI workflow for this pull request because 3 jobs were identified as retry-safe transient failures in the CI run attempt.
Matched test failure patterns (1 test)
|
Add BenchmarkDotNet coverage for dashboard trace filtering and optimize the Trace Detail filter path by avoiding recursive subtree scans, building parent maps in one pass, materializing grid data once, and comparing duration thresholds numerically. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add BenchmarkDotNet scenarios for TelemetryRepository trace ingestion and query filtering over 10k spans, including duration and no-match filters. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cover dynamic filters combined with duration filtering, paging, total count, and max duration before optimizing TelemetryRepository.GetTraces. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Scan traces once when filtering, counting, paging, and computing max duration. Precompile common field filters so duration comparisons avoid per-span string formatting and parsing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
❓ CLI E2E Tests unknown — 95 passed, 0 failed, 5 unknown (commit View all recordings
📹 Recordings uploaded automatically from CI run #26275480593 |


Description
Adds minimum span duration filtering so performance profiling can hide very short spans that add noise to trace analysis.
Users can filter spans by duration in three places:
Duration (ms)from the existing filter dialog. Numeric duration fields use numeric input and comparison operators (>=,>,<=,<) instead of string combobox operators.minDurationMsto span and trace endpoints.--min-durationor--min-duration-mstoaspire telemetry spansandaspire telemetry traces.Trace Detail behavior
The duration filter is intentionally a per-span profiling noise filter:
This makes it useful for profiling large traces where a slow parent can contain many tiny implementation-detail spans that should be hidden.
Performance work
This PR also adds Dashboard BenchmarkDotNet coverage for span filtering and
TelemetryRepository.GetTracesquery/filter scenarios.TelemetryRepository.GetTracesnow scans traces once for filtering, paging, total count, andMaxDuration, while only cloning returned page items. Duration filters compare parsed numeric thresholds directly instead of formatting/parsing every span duration.Benchmark result for the intended profiling miss case over 10k spans:
Matching/no-filter queries are still dominated by cloning the returned page, so those scenarios are roughly unchanged.
User-facing usage
Dashboard Trace Detail:
Duration (ms).>=.50.Dashboard telemetry API:
CLI:
Command help:
Validation
dotnet test --project tests/Aspire.Dashboard.Tests/Aspire.Dashboard.Tests.csproj --no-launch-profile -- --filter-class "*.TelemetryApiServiceTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"dotnet test --project tests/Aspire.Dashboard.Tests/Aspire.Dashboard.Tests.csproj --no-launch-profile -- --filter-class "*.TelemetryApiServiceTests" --filter-class "*.DashboardUrlsTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"dotnet test --project tests/Aspire.Dashboard.Tests/Aspire.Dashboard.Tests.csproj --no-launch-profile -- --filter-class "*.TraceTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"dotnet test --project tests/Aspire.Cli.Tests/Aspire.Cli.Tests.csproj --no-launch-profile -- --filter-class "*.TelemetrySpansCommandTests" --filter-class "*.TelemetryTracesCommandTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"dotnet test --project tests/Aspire.Dashboard.Components.Tests/Aspire.Dashboard.Components.Tests.csproj --no-launch-profile -- --filter-class "*.FilterDialogTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"dotnet test --project tests/Aspire.Dashboard.Components.Tests/Aspire.Dashboard.Components.Tests.csproj --no-launch-profile -- --filter-class "*.TraceDetailsTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"dotnet test --project tests/Aspire.Dashboard.Components.Tests/Aspire.Dashboard.Components.Tests.csproj --no-launch-profile -- --filter-class "*.SpanWaterfallViewModelTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"dotnet build benchmarks/Aspire.Dashboard.Benchmarks/Aspire.Dashboard.Benchmarks.csproj -c Release --no-restoredotnet run -c Release --project benchmarks/Aspire.Dashboard.Benchmarks/Aspire.Dashboard.Benchmarks.csproj --filter '*TelemetryRepositoryBenchmarks*' --joindotnet run -c Release --project benchmarks/Aspire.Dashboard.Benchmarks/Aspire.Dashboard.Benchmarks.csproj --filter '*SpanFilteringBenchmarks*' --joinotel spans --min-duration 50otel traces --trace-id <trace-id> --min-duration 50dotnet build /t:UpdateXlf src/Aspire.Dashboard/Aspire.Dashboard.csprojgit diff --checkChecklist
<remarks />and<code />elements on your triple slash comments?